/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import type { HvigorCoreNode } from '../../../external/core/hvigor-core-node.js';
import type { IncrementalTask } from '../../../external/task/incremental-task.js';
import { createHash } from '@ohos/hvigor-common';
import { getTaskSnapCacheEntryUniqueKey } from '../../cache/util/cache-util.js';
import type { FsOptions } from '../util/file-set.js';
import { TaskBuiltIn } from '../util/task-built-in.js';
import { TaskInputValue, ValueEntry } from '../util/task-input-value-entry.js';
import { TaskSnapshot } from './task-snapshot.js';
import snapshotSerializationReplacer = TaskBuiltIn.snapshotSerializationReplacer;
import { isEqual } from '@baize/wdk';

import type { ProjectCacheService } from '../../cache/project-cache-service.js';

export class DefaultTaskSnapshot implements TaskSnapshot {
  private readonly _inputs: ValueEntry[] = [];
  private readonly _projectName: string;
  private readonly _moduleName: string;
  private readonly _taskName: string;
  private readonly _key: string;
  private _successful = false;
  private readonly _executionId: string;
  private readonly _inputFiles: Map<string, FsOptions>;
  private readonly _outputFiles: Map<string, FsOptions>;

  constructor(task: IncrementalTask) {
    const node: HvigorCoreNode = task.getNode();
    this._projectName = node.getProject().getName();
    this._moduleName = node.getName();
    this._taskName = task.getName();
    this._key = getTaskSnapCacheEntryUniqueKey(this._taskName, node);
    this._executionId = `${this._key}:${Date.now()}`;

    task.declareInputs().forEach((value, key) => this.addInput(key, value));
    this._inputFiles = task.declareInputFiles().collect();
    this._outputFiles = task.declareOutputFiles().collect();
  }

  getKey(): string {
    return this._key;
  }

  compareTo(other: TaskSnapshot): boolean {
    return (
      this._successful &&
      this._projectName === other.getProjectName() &&
      this._moduleName === other.getModuleName() &&
      this._taskName === other.getTaskName() &&
      this.compareInputs(other) &&
      this.compareInputFiles(other) &&
      this.compareOutputFiles(other)
    );
  }

  /**
   * 使用新生成的FileSnapShot保存到最终的TaskSnapShot
   *
   * @param {ProjectCacheService} projectCacheService
   */
  updateFilesOptions(projectCacheService: ProjectCacheService) {
    this._inputFiles.forEach((value, key) => {
      value.fileSnapShotHashValue = projectCacheService.currentFileSnapShotCacheService.get(key)?.hashValue;
    });

    this._outputFiles.forEach((value, key) => {
      value.fileSnapShotHashValue = projectCacheService.currentFileSnapShotCacheService.get(key)?.hashValue;
    });
  }

  /**
   * 使用Cache的FileSnapShot更新新生成的TaskSnapShot
   *
   * @param {ProjectCacheService} projectCacheService
   */
  updateCacheFilesOptions(projectCacheService: ProjectCacheService) {
    this._inputFiles.forEach((value, key) => {
      value.fileSnapShotHashValue = projectCacheService.fileSnapShotCacheService.get(key)?.hashValue;
    });

    this._outputFiles.forEach((value, key) => {
      value.fileSnapShotHashValue = projectCacheService.fileSnapShotCacheService.get(key)?.hashValue;
    });
  }

  getModuleName(): string {
    return this._moduleName;
  }

  getTaskName(): string {
    return this._taskName;
  }

  getProjectName(): string {
    return this._projectName;
  }

  isSuccessful(): boolean {
    return this._successful;
  }

  setExecutedSuccessful(): void {
    this._successful = true;
  }

  addInput(key: string, value: TaskInputValue): void {
    const index = this._inputs.findIndex((value) => {
      return value.getName() === key;
    });
    if (index < 0) {
      this._inputs.push(new ValueEntry(key, value));
    } else {
      this._inputs.splice(index, 1);
      this._inputs.push(new ValueEntry(key, value));
    }
  }

  getInputs(): ValueEntry[] {
    return this._inputs;
  }

  getExecutionId(): string {
    return this._executionId;
  }

  private compareInputs(other: TaskSnapshot): boolean {
    const lastInputs = other.getInputs();
    if (this._inputs.length !== lastInputs.length) {
      return false;
    }
    const comparator = (entry: ValueEntry, other: ValueEntry) => {
      return entry.getName() > other.getName() ? 1 : other.getName() > entry.getName() ? -1 : 0;
    };
    let hash = createHash();
    this._inputs.sort(comparator).forEach((value) => hash.update(value.getHash()));
    const thisInputValue = hash.digest('hex');
    hash = createHash();
    other
      .getInputs()
      .sort(comparator)
      .forEach((value) => hash.update(value.getHash()));
    const lastInputsValue = hash.digest('hex');
    return thisInputValue === lastInputsValue;
  }

  private compareInputFiles(other: TaskSnapshot): boolean {
    return DefaultTaskSnapshot.compareFiles(this._inputFiles, other.getInputFiles());
  }

  private compareOutputFiles(other: TaskSnapshot): boolean {
    return DefaultTaskSnapshot.compareFiles(this._outputFiles, other.getOutputFiles());
  }

  private static compareFiles(files: Map<string, FsOptions>, otherFiles: Map<string, FsOptions>): boolean {
    if (files.size !== otherFiles.size) {
      return false;
    }
    for (const [filePath, fsOptions] of files) {
      if (!otherFiles.has(filePath)) {
        return false;
      }
      const otherFsOptions = otherFiles.get(filePath);
      if (!isEqual(fsOptions, otherFsOptions)) {
        return false;
      }
    }
    return true;
  }

  getInputFiles(): Map<string, FsOptions> {
    return this._inputFiles;
  }

  getOutputFiles(): Map<string, FsOptions> {
    return this._outputFiles;
  }

  stringify(): string {
    return JSON.stringify(this, snapshotSerializationReplacer);
  }
}
